#include "abstractgraphiceditor.h"

#include "../documents/data/graphicdata.h"
#include "../undo/graphicdatacommands.h"
#include "../utils/undomanager.h"
#include "graphiceditorcommon.h"
#include "items/graphicitemcommon.h"

#include <cmath>
#include <focusevent.h>
#include <iconmanager.h>
#include <ieditoritem.h>
#include <ilinkeddocument.h>

#include <QtCore/QEvent>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QGraphicsView>
#include <QtGui/QMouseEvent>
#include <QtGui/QToolBar>
#include <QtGui/QVBoxLayout>
#include <QtGui/QWheelEvent>

using namespace core;
using namespace gui;
using namespace swp;

AbstractGraphicEditor::AbstractGraphicEditor(QWidget* parent,
    Palette* palette) :
        QFrame(parent), IEditor(),
        m_focusListeners(),
        m_document(),
        m_documentData(0),
        m_selectMode(0),
        m_transformMode(0),
        m_escapeNonDisplayableMode(0),
        m_escapeLastAnchor(0),
        m_zoomIn(0),
        m_zoomOut(0),
        m_zoomInMode(0),
        m_zoomOutMode(0),
        m_scaleToFitContent(0),
        m_removeSelected(0),
        m_refreshLinkedModels(0),
        m_selectAll(0),
        m_deselectAll(0),
        m_duplicateItem(0),
        m_connectCursor(Qt::CrossCursor),
        m_selectCursor(Qt::ArrowCursor),
        m_transformCursor(Qt::SizeFDiagCursor),
        m_zoomInCursor(Qt::CrossCursor),
        m_zoomOutCursor(Qt::CrossCursor),
        m_zoomLevel(0),
        m_view(0),
        m_toolbar(0),
        m_palette(palette),
        m_currentScale(1.0),
        m_isInZoomInMode(false),
        m_isInZoomOutMode(false)
{
    m_view = new QGraphicsView;
    m_view->setFocusPolicy(Qt::StrongFocus);
    m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);

    m_view->installEventFilter(this);

    m_toolbar = new QToolBar;
    m_toolbar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);

    layout->addWidget(m_toolbar);
    layout->addWidget(m_view);

    setLayout(layout);

    IconManager* iconManager = IconManager::getInstance();
    iconManager->registerStyleIcon("SwpSelectionMode",
        ":/swp/icons/selection_mode_icon.png");
    iconManager->registerStyleIcon("SwpTransformationMode",
        ":/swp/icons/transformation_mode_icon.png");
    iconManager->registerStyleIcon("SwpZoomIn",
        ":/swp/icons/zoom_in_icon.png");
    iconManager->registerStyleIcon("SwpZoomOut",
        ":/swp/icons/zoom_out_icon.png");
    iconManager->registerStyleIcon("SwpScaleToFit",
        ":/swp/icons/scale_to_fit_icon.png");
    iconManager->registerStyleIcon("SwpRemoveSelected",
        ":/swp/icons/remove_selected_icon.png");
    iconManager->registerStyleIcon("SwpRefreshLinkedModels",
        ":/swp/icons/refresh_linked_documents_icon.png");

    iconManager->registerStyleIcon("SwpConnectCursor",
        ":/swp/cursors/connect_cursor.png");
    iconManager->registerStyleIcon("SwpConnectCursorMask",
        ":/swp/cursors/connect_cursor_mask.png");
    iconManager->registerStyleIcon("SwpSelectCursor",
        ":/swp/cursors/select_cursor.png");
    iconManager->registerStyleIcon("SwpSelectCursorMask",
        ":/swp/cursors/select_cursor_mask.png");
    iconManager->registerStyleIcon("SwpTransformCursor",
        ":/swp/cursors/transform_cursor.png");
    iconManager->registerStyleIcon("SwpTransformCursorMask",
        ":/swp/cursors/transform_cursor_mask.png");
    iconManager->registerStyleIcon("SwpZoominCursor",
        ":/swp/cursors/zoomin_cursor.png");
    iconManager->registerStyleIcon("SwpZoominCursorMask",
        ":/swp/cursors/zoomin_cursor_mask.png");
    iconManager->registerStyleIcon("SwpZoomoutCursor",
        ":/swp/cursors/zoomout_cursor.png");
    iconManager->registerStyleIcon("SwpZoomoutCursorMask",
        ":/swp/cursors/zoomout_cursor_mask.png");

    createActions();
    connectSlots();
    createCursors();
}

AbstractGraphicEditor::~AbstractGraphicEditor()
{
    delete m_palette;
}

void AbstractGraphicEditor::open(const QSharedPointer<IDocument>& document)
{
    m_document = document;

    if (m_document->isLinked())
    {
        m_refreshLinkedModels = new QAction(tr("Refresh linked models"),
            m_toolbar);
        m_refreshLinkedModels->setShortcut(QKeySequence(
            Qt::CTRL + Qt::Key_F5));

        IconManager* iconManager = IconManager::getInstance();
        if (iconManager->hasIcon("SwpRefreshLinkedModels"))
        {
            m_refreshLinkedModels->setIcon(
                iconManager->icon("SwpRefreshLinkedModels"));
        }

        m_toolbar->addSeparator();
        m_toolbar->addAction(m_refreshLinkedModels);

        connect(m_refreshLinkedModels, SIGNAL(triggered()),
            this, SLOT(refreshLinkedModels()));
    }

    m_documentData = dynamic_cast<GraphicData* >(document->data());

    Q_ASSERT(!m_document.isNull() && m_documentData != 0);

    m_documentData->setGraphicPalette(m_palette);
    m_view->setScene(m_documentData);
    m_view->centerOn(0, 0);
}

void AbstractGraphicEditor::close(const QSharedPointer<IDocument>& document)
{

}

QSharedPointer<IDocument> AbstractGraphicEditor::document() const
{
    return m_document;
}

QWidget* AbstractGraphicEditor::widget() const
{
    return const_cast<AbstractGraphicEditor* >(this);
}

void AbstractGraphicEditor::registerFocusListener(QObject* object)
{
    if (!m_focusListeners.contains(object))
    {
        m_focusListeners.append(object);
    }
}

void AbstractGraphicEditor::unregisterFocusListener(QObject* object)
{
    if (m_focusListeners.contains(object))
    {
        m_focusListeners.removeAll(object);
    }
}

bool AbstractGraphicEditor::eventFilter(QObject* watched, QEvent* event)
{
    bool ret = false;

    if (watched == m_view && event->type() == QEvent::FocusIn)
    {
        sendFocusEvent();
        ret = true;
    }
    else if (watched == m_view && event->type() == QEvent::FocusOut)
    {
        sendFocusEvent(false);
        ret = true;
    }
    else if (watched == m_view->viewport() &&
        event->type() == QEvent::MouseButtonPress &&
        (m_isInZoomInMode || m_isInZoomOutMode))
    {
        QMouseEvent* _event = dynamic_cast<QMouseEvent*>(event);

        if (m_isInZoomInMode)
        {
            zoomIn(m_view->mapToScene(_event->pos()));
        }
        else
        {
            zoomOut(m_view->mapToScene(_event->pos()));
        }

        ret = true;
    }
    else if (watched == m_view->viewport() && event->type() == QEvent::Wheel)
    {
        QWheelEvent* _event = dynamic_cast<QWheelEvent*>(event);

        if ((_event->modifiers() & Qt::ControlModifier) ==
            Qt::ControlModifier && _event->delta() != 0)
        {
            if (_event->delta() > 0)
            {
                zoomIn(m_view->mapToScene(_event->pos()));
            }
            else
            {
                zoomOut(m_view->mapToScene(_event->pos()));
            }

            ret = true;
        }
        else
        {
            ret = QFrame::eventFilter(watched, event);
        }
    }
    else
    {
        ret = QFrame::eventFilter(watched, event);
    }

    return ret;
}

RemoveConnectionCommand* AbstractGraphicEditor::removeConnectionCommand(
    IGraphicEditorItem* item) const
{
    RemoveConnectionCommand* ret = new RemoveConnectionCommand(
        m_documentData, item, m_documentData->connectionData(item));

    return ret;
}
RemoveGraphicItemCommand* AbstractGraphicEditor::removeItemCommand(
    IGraphicEditorItem* item) const
{
    RemoveGraphicItemCommand* ret = new RemoveGraphicItemCommand(
        m_documentData, item);

    return ret;
}

void AbstractGraphicEditor::createActions()
{
    IconManager* iconManager = IconManager::getInstance();

    m_selectMode = new QAction(tr("Selection mode"), m_toolbar);
    m_selectMode->setShortcut(QKeySequence(Qt::Key_V));
    if (iconManager->hasIcon("SwpSelectionMode"))
    {
        m_selectMode->setIcon(iconManager->icon("SwpSelectionMode"));
    }

    m_transformMode = new QAction(tr("Transformation mode"), m_toolbar);
    m_transformMode->setShortcut(QKeySequence(Qt::Key_T));
    if (iconManager->hasIcon("SwpTransformationMode"))
    {
        m_transformMode->setIcon(iconManager->icon("SwpTransformationMode"));
    }

    m_escapeNonDisplayableMode = new QAction(tr("Escape mode"), this);
    m_escapeNonDisplayableMode->setShortcut(QKeySequence(Qt::Key_Escape));

    m_escapeLastAnchor = new QAction(tr("Escape mode"), this);
    m_escapeLastAnchor->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Z));

    m_zoomIn = new QAction(tr("Zoom in"), this);
    m_zoomIn->setShortcut(QKeySequence::ZoomIn);
    m_zoomOut = new QAction(tr("Zoom out"), this);
    m_zoomOut->setShortcut(QKeySequence::ZoomOut);

    m_selectAll = new QAction(tr("Select all"), this);
    m_selectAll->setShortcut(QKeySequence::SelectAll);
    m_deselectAll = new QAction(tr("Unselect all"), this);
    m_deselectAll->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
    m_duplicateItem = new QAction(tr("Duplicate item"), this);
    m_duplicateItem->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));

    m_zoomInMode = new QAction(tr("Zoom in mode"), m_toolbar);
    if (iconManager->hasIcon("SwpZoomIn"))
    {
        m_zoomInMode->setIcon(iconManager->icon("SwpZoomIn"));
    }
    m_zoomOutMode = new QAction(tr("Zoom out mode"), m_toolbar);
    if (iconManager->hasIcon("SwpZoomOut"))
    {
        m_zoomOutMode->setIcon(iconManager->icon("SwpZoomOut"));
    }

    m_scaleToFitContent = new QAction(tr("Scale to fit content"), m_toolbar);
    m_scaleToFitContent->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT +
        Qt::Key_S));
    if (iconManager->hasIcon("SwpScaleToFit"))
    {
        m_scaleToFitContent->setIcon(iconManager->icon("SwpScaleToFit"));
    }

    m_removeSelected = new QAction(tr("Remove selected items"), m_toolbar);
    m_removeSelected->setShortcut(QKeySequence(Qt::Key_Delete));
    if (iconManager->hasIcon("SwpRemoveSelected"))
    {
        m_removeSelected->setIcon(iconManager->icon("SwpRemoveSelected"));
    }

    m_zoomLevel = new QComboBox;

    for (qreal it = PREFERRED_MIN_ZOOM; it <= PREFERRED_MAX_ZOOM;
        it += PREFERRED_ZOOM_STEP)
    {
        const int level = (it * 100) + 0.5;
        m_zoomLevel->addItem(QString("%1%").arg(level));
    }

    m_zoomLevel->setCurrentIndex(indexForScale(1));

    m_transformMode->setEnabled(false);

    m_toolbar->addAction(m_selectMode);
    //m_toolbar->addAction(m_transformMode);
    m_toolbar->addSeparator();
    m_toolbar->addWidget(m_zoomLevel);
    m_toolbar->addAction(m_zoomInMode);
    m_toolbar->addAction(m_zoomOutMode);
    m_toolbar->addSeparator();
    m_toolbar->addAction(m_scaleToFitContent);
    m_toolbar->addAction(m_removeSelected);

    addAction(m_escapeNonDisplayableMode);
    addAction(m_escapeLastAnchor);
    addAction(m_zoomIn);
    addAction(m_zoomOut);
    addAction(m_selectAll);
    addAction(m_deselectAll);
    addAction(m_duplicateItem);
}

void AbstractGraphicEditor::connectSlots()
{
    connect(m_selectMode, SIGNAL(triggered()),
        this, SLOT(setModeToSelect()));
    connect(m_transformMode, SIGNAL(triggered()),
        this, SLOT(setModeToTransform()));
    connect(m_escapeNonDisplayableMode, SIGNAL(triggered()),
        this, SLOT(escapeNonDisplayableMode()));
    connect(m_escapeLastAnchor, SIGNAL(triggered()),
        this, SLOT(escapeLastAnchor()));
    connect(m_zoomLevel, SIGNAL(currentIndexChanged(int)),
            this, SLOT(changeZoomLevel()));
    connect(m_zoomIn, SIGNAL(triggered()),
        this, SLOT(zoomIn()));
    connect(m_zoomOut, SIGNAL(triggered()),
        this, SLOT(zoomOut()));
    connect(m_zoomInMode, SIGNAL(triggered()),
        this, SLOT(setModeToZoomIn()));
    connect(m_zoomOutMode, SIGNAL(triggered()),
        this, SLOT(setModeToZoomOut()));
    connect(m_scaleToFitContent, SIGNAL(triggered()),
        this, SLOT(scaleToFitContent()));
    connect(m_removeSelected, SIGNAL(triggered()),
        this, SLOT(removeSelectedItems()));
    connect(m_selectAll, SIGNAL(triggered()),
        this, SLOT(selectAll()));
    connect(m_deselectAll, SIGNAL(triggered()),
        this, SLOT(unselectAll()));
    connect(m_duplicateItem, SIGNAL(triggered()),
        this, SLOT(duplicateItem()));
}

void AbstractGraphicEditor::createCursors()
{
    IconManager* iconManager = IconManager::getInstance();

    if (iconManager->hasIcon("SwpConnectCursor") &&
        iconManager->hasIcon("SwpConnectCursorMask"))
    {
        QPixmap pixmap(iconManager->icon("SwpConnectCursor").pixmap(24, 24));
        pixmap.setMask(QBitmap(QPixmap(
            iconManager->icon("SwpConnectCursorMask").pixmap(24, 24))));
        m_connectCursor = QCursor(pixmap, 0, 0);
    }

    if (iconManager->hasIcon("SwpSelectCursor") &&
        iconManager->hasIcon("SwpSelectCursorMask"))
    {
        QPixmap pixmap(iconManager->icon("SwpSelectCursor").pixmap(24, 24));
        pixmap.setMask(QBitmap(QPixmap(
            iconManager->icon("SwpSelectCursorMask").pixmap(24, 24))));
        m_selectCursor = QCursor(pixmap, 0, 0);
    }

    if (iconManager->hasIcon("SwpTransformCursor") &&
        iconManager->hasIcon("SwpTransformCursorMask"))
    {
        QPixmap pixmap(
            iconManager->icon("SwpTransformCursor").pixmap(24, 24));
        pixmap.setMask(QBitmap(QPixmap(
            iconManager->icon("SwpTransformCursorMask").pixmap(24, 24))));
        m_transformCursor = QCursor(pixmap, 0, 0);
    }

    if (iconManager->hasIcon("SwpZoominCursor") &&
        iconManager->hasIcon("SwpZoominCursorMask"))
    {
        QPixmap pixmap(
            iconManager->icon("SwpZoominCursor").pixmap(24, 24));
        pixmap.setMask(QBitmap(QPixmap(
            iconManager->icon("SwpZoominCursorMask").pixmap(24, 24))));
        m_zoomInCursor = QCursor(pixmap, 0, 0);
    }

    if (iconManager->hasIcon("SwpZoomoutCursor") &&
        iconManager->hasIcon("SwpZoomoutCursorMask"))
    {
        QPixmap pixmap(
            iconManager->icon("SwpZoomoutCursor").pixmap(24, 24));
        pixmap.setMask(QBitmap(QPixmap(
            iconManager->icon("SwpZoomoutCursorMask").pixmap(24, 24))));
        m_zoomOutCursor = QCursor(pixmap, 0, 0);
    }

    m_view->setCursor(m_selectCursor);
}

void AbstractGraphicEditor::sendFocusEvent(bool focusIn) const
{
    FocusEvent* event = new FocusEvent(focusIn);

    foreach (QObject* listener, m_focusListeners)
    {
        if (!event->isAccepted())
        {
            QApplication::sendEvent(listener, event);
        }
        else
        {
            break;
        }
    }

    delete event;
}

void AbstractGraphicEditor::cancelUnfinishedConnection(bool resetMode)
{
    if (m_documentData->inUnfinishedConnection())
    {
        m_documentData->cancelUnfinishedConnection();
    }
    else
    {
        m_documentData->cancelUnfinishedConnection(true);

        if (resetMode)
        {
            setModeToSelect();
        }
    }
}

int AbstractGraphicEditor::indexForScale(qreal zoomLevel) const
{
    const int ret = ((zoomLevel - PREFERRED_MIN_ZOOM) /
            PREFERRED_ZOOM_STEP) + 0.5;

    return ret;
}

// protected slots

void AbstractGraphicEditor::setModeToEdit()
{
    m_isInZoomInMode = false;
    m_isInZoomOutMode = false;
    cancelUnfinishedConnection();
    m_documentData->setCurrentState(Edit);
}

void AbstractGraphicEditor::setModeToConnect(const ConnectionData& data)
{
    m_isInZoomInMode = false;
    m_isInZoomOutMode = false;
    cancelUnfinishedConnection();
    m_view->setCursor(m_connectCursor);
    m_documentData->setConnectionData(data);
    m_documentData->setCurrentState(Connect);
}

void AbstractGraphicEditor::setModeToSelect()
{
    m_isInZoomInMode = false;
    m_isInZoomOutMode = false;
    cancelUnfinishedConnection();
    m_view->setCursor(m_selectCursor);
    m_documentData->setCurrentState(SelectMove);
}

void AbstractGraphicEditor::setModeToTransform()
{
    m_isInZoomInMode = false;
    m_isInZoomOutMode = false;
    cancelUnfinishedConnection();
    m_view->setCursor(m_transformCursor);
    m_documentData->setCurrentState(Transform);
}

void AbstractGraphicEditor::setModeToZoomIn()
{
    m_isInZoomOutMode = false;
    m_isInZoomInMode = true;
    m_view->setCursor(m_zoomInCursor);
}

void AbstractGraphicEditor::setModeToZoomOut()
{
    m_isInZoomInMode = false;
    m_isInZoomOutMode = true;
    m_view->setCursor(m_zoomOutCursor);
}

// private slots
void AbstractGraphicEditor::changeZoomLevel()
{
    const int index = m_zoomLevel->currentIndex();
    const qreal zoom = index * PREFERRED_ZOOM_STEP  + PREFERRED_MIN_ZOOM;

    m_currentScale = zoom;
    m_view->setTransform(
        QTransform::fromScale(m_currentScale, m_currentScale));
}

void AbstractGraphicEditor::zoomIn(const QPointF& center)
{
    if (center.x() != -1 && center.y() != -1)
    {
        m_view->centerOn(center);
    }

    const qreal newValue = m_currentScale + PREFERRED_ZOOM_STEP;
    if (newValue < PREFERRED_MAX_ZOOM ||
        fabs(newValue - PREFERRED_MAX_ZOOM) < 0.01)
    {
        m_currentScale += PREFERRED_ZOOM_STEP;

        const int index = indexForScale(m_currentScale);

        m_zoomLevel->setCurrentIndex(index);
    }
}

void AbstractGraphicEditor::zoomOut(const QPointF& center)
{
    if (center.x() != -1 && center.y() != -1)
    {
        m_view->centerOn(center);
    }

    const qreal newValue = m_currentScale - PREFERRED_ZOOM_STEP;
    if (newValue > PREFERRED_MIN_ZOOM ||
        fabs(newValue - PREFERRED_MIN_ZOOM) < 0.01)
    {
        m_currentScale -= PREFERRED_ZOOM_STEP;

        const int index = indexForScale(m_currentScale);

        m_zoomLevel->setCurrentIndex(index);
    }
}

void AbstractGraphicEditor::scaleToFitContent()
{
    m_documentData->scaleSelectionToFitContent();
}

void AbstractGraphicEditor::removeSelectedItems()
{
    const QList<IGraphicEditorItem* > items =
        m_documentData->selectedGraphicItems();

    foreach (IGraphicEditorItem* item, items)
    {
        QUndoCommand* command = 0;

        if (isConnectorItem(item->type()))
        {
            command = removeConnectionCommand(item);
        }
        else
        {
            command = removeItemCommand(item);
        }

        UndoManager::getInstance()->push(m_documentData, command);
    }
}

void AbstractGraphicEditor::escapeNonDisplayableMode()
{
    cancelUnfinishedConnection(true);
}

void AbstractGraphicEditor::escapeLastAnchor()
{
    if (m_documentData->inUnfinishedConnection())
    {
        m_documentData->removeLastConnectorAnchor();
    }
}

void AbstractGraphicEditor::refreshLinkedModels()
{
    QSharedPointer<ILinkedDocument> document =
        qSharedPointerDynamicCast<ILinkedDocument>(m_document);

    Q_ASSERT(!document.isNull()); // can't happen

    document->refreshLinkedModels();
}

void AbstractGraphicEditor::selectAll()
{
    m_documentData->selectAll();
}

void AbstractGraphicEditor::unselectAll()
{
    m_documentData->unselectAll();
}

void AbstractGraphicEditor::duplicateItem()
{
    m_documentData->duplicateSelectedItem();
}
